/*
 * Model
 *
 *  Created on: 10/06/2011
 *      Author: Agustina I. Stekolschik & Ezequiel Palumbo
 */

#include <StructModel.h>
#include <Logging.h>
#include <vector>
#include <LevelContext.h>

using logging::Logger;
using logging::LoggerFactory;
using logging::Level;

using namespace std;
using namespace contexts;

namespace model{

StructModel::StructModel(CodingLogicProvider& provider){
	this->codingLogicProvider = &provider;
}

void StructModel::initContexts(){
	Logger& logger = LoggerFactory::getInstance().getLogger("Model.StructModel");
	logger.log(logging::DEBUG, "Starting level contexts creation.");

	vector<DataUnit*> dataUnitsVector;
	unsigned int startLevelChar = 0;
	unsigned int levelNumber = 0;

	//Level 0
	dataUnitsVector.push_back(new CharDataUnit(startLevelChar));
	this->contexts.push_back(new LevelContext(dataUnitsVector));


	startLevelChar = 1;
	// Higher levels
	for (levelNumber = 1; levelNumber <= 8; levelNumber++) {
		dataUnitsVector.clear();
		for (unsigned int aChar = startLevelChar; aChar < startLevelChar * 2; aChar++) {
			dataUnitsVector.push_back(new CharDataUnit(aChar));
		}
		if (levelNumber == 8) {
			dataUnitsVector.push_back(new EndOfFileDataUnit());
		}
		this->contexts.push_back(new LevelContext(dataUnitsVector));
		startLevelChar += startLevelChar;
	}
}

// Ok, this is the core of our compression procedure. Magic, do not touch.
void StructModel::compress(DataInput* dataInput, BitDataOutput* bitDataOutput){
	Logger& logger = LoggerFactory::getInstance().getLogger("Model.StructModel");
	logger.log(logging::DEBUG, "Compression is starting...");

	logger.log(logging::TRACE, "Lookup Coder.");
	Coder* coder = this->codingLogicProvider->getCoder();
	logger.log(logging::TRACE, "Setting DataOutput to Coder.");
	coder->setDataOutput(*bitDataOutput);

	bool thereIsMoreToCompress = true;
	bool locatedOnLevel;
	logger.log(logging::TRACE, "Ready to start compression loop.");
	char auxCharArray[] = {0,0,0,0};
	DataUnit* currentDataUnit;
	while (thereIsMoreToCompress) {

			logger.log(logging::TRACE, "Getting the next DataUnit.");
			try {
				currentDataUnit = dataInput->getNext();
			} catch (EndOfDataException& eode) {
				logger.log(logging::DEBUG, "End of Data was reached.");
				thereIsMoreToCompress = false;
				//Add the end of file to the compressed file
				//logger.log(logging::TRACE, "Encoding EOF.");
				currentDataUnit = new EndOfFileDataUnit;
			}
			logger.log(logging::TRACE, "The DataUnit returned by DataInput is: ");
                        for (int i=0 ; i < sizeof(auxCharArray) ; i++) {
                        	auxCharArray[i] = 0;
                        }
			sprintf(auxCharArray, "%d", currentDataUnit->asInt());
			logger.log(logging::TRACE, auxCharArray);
			unsigned int currentTriedLevel = 0;
			locatedOnLevel = false;
			logger.log(logging::TRACE, "Ready to start searching level loop for current DataUnit.");
			do {
				logger.log(logging::TRACE, "Asking level for DataUnit, current level is:");
                                for (int i=0 ; i < sizeof(auxCharArray) ; i++) {
                                        auxCharArray[i] = 0;
                                }      
                                sprintf(auxCharArray, "%d", currentTriedLevel);
				logger.log(logging::TRACE, auxCharArray);
				ContextUnit& theContextUnit = this->contexts.at(currentTriedLevel)
						->getContextUnitFor(*currentDataUnit);
				logger.log(logging::TRACE, "Encoding the returned ContextUnit.");
				coder->encode(theContextUnit);
				logger.log(logging::TRACE, "Updating its frequency.");
				theContextUnit.updateFrequency();
				locatedOnLevel = !(&(this->contexts.at(currentTriedLevel)->getEscape())
						== &theContextUnit);
				currentTriedLevel++;
				logger.log(logging::TRACE, "End of searching level loop iteration.");
			} while (!locatedOnLevel);
			delete currentDataUnit;

		logger.log(logging::TRACE, "End of compression loop iteration reached.");
	}
	logger.log(logging::DEBUG, "End of compression process was reached.");
	delete coder;
}

// This has to be magic too, hasn't it?
void StructModel::decompress(BitDataInput* bitDataInput, DataOutput* dataOutput, DataUnitFactory* dataUnitFactory){
	Logger& logger = LoggerFactory::getInstance().getLogger("Model.StructModel");
	logger.log(logging::DEBUG, "Decompression is starting...");

	Decoder* decoder = this->codingLogicProvider->getDecoder();
	decoder->setDataInput(*bitDataInput);
	decoder->setDataUnitFactory(*dataUnitFactory);

	bool thereIsMoreToDecompress = true;
	bool locatedOnLevel = false;
	DataUnit* currentDataUnit;
	EndOfFileDataUnit eof;
	char auxCharArray[] = {0,0,0,0};
	while (thereIsMoreToDecompress){
		unsigned int currentTriedLevel = 0;
		locatedOnLevel = false;
		do {
			currentDataUnit = decoder->decodeNext(*(this->contexts.at(currentTriedLevel)));
			logger.log(logging::TRACE, "The DataUnit returned by DataInput is: ");
                        for (int i=0 ; i < sizeof(auxCharArray) ; i++) {
                        	auxCharArray[i] = 0;
                        }
			sprintf(auxCharArray, "%d", currentDataUnit->asInt());
			logger.log(logging::TRACE, auxCharArray);
			DataUnit* escape = &(this->contexts.at(currentTriedLevel)->getEscape().getDataUnit());
			if (!(*currentDataUnit == *escape)) {
				locatedOnLevel = true;
			} else {
				currentTriedLevel++;
				delete currentDataUnit;
			}
		} while (!locatedOnLevel);

		if((*currentDataUnit) == eof){
			thereIsMoreToDecompress = false;
		}else{
			dataOutput->append(currentDataUnit);
		}
	}
}

}
